﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;

using System.ComponentModel.Composition.Hosting;
using System.IO;
using System.Reflection;
using System.Web.Mvc;
using System.Web.Routing;

namespace org.theGecko.Utilities.MefMvc
{
    /// <summary>
    /// Main plugin registration code
    /// </summary>
    public class MefMvcPluginProvider
    {
        private readonly string _pluginPath;
        private readonly string _fullPluginPath;
        private readonly string _defaultMaster;

        private ISet<IPluginRegistration> _plugins;

        public MefMvcPluginProvider(string pluginPath)
            : this(pluginPath, "Site")
        {
        }


        public MefMvcPluginProvider(string pluginPath, string defaultMaster)
        {
            _pluginPath = pluginPath;
            _fullPluginPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, _pluginPath);
            _defaultMaster = defaultMaster;

        }

        public ISet<IPluginRegistration> Plugins
        {
            get
            {
                return _plugins;
            }
        }

        public string PluginPath
        {
            get { return _pluginPath; }
        }
        #region IMvcPluginProvider Members

        public void Initialize()
        {
            // Add assembly handler for strongly-typed view models
            AppDomain.CurrentDomain.AssemblyResolve += PluginAssemblyResolve;

            //Possibilita incluir os plugins por pastas
            IEnumerable<DirectoryCatalog> listCatalog = Directory.EnumerateDirectories(_fullPluginPath).Select(p => new DirectoryCatalog(p, @"bin\*.dll"));

            AggregateCatalog catalog = new AggregateCatalog(listCatalog);


            CompositionContainer container = new CompositionContainer(catalog);

            // Retrieve plugins in priority order
            //ISet<IPluginRegistration> plugins 
            _plugins = new SortedSet<IPluginRegistration>(new PluginComparer());
            foreach (IPluginRegistration import in container.GetExportedValues<IPluginRegistration>())
            {
                _plugins.Add(import);
            }

            // Register plugins
            foreach (IPluginRegistration plugin in _plugins)
            {
                //plugin.RegisterRoutes(RouteTable.Routes);
            }

            // Set the controller factory

            MefContext context = new MefContext(container, _plugins);
            IControllerFactory factory = new MefControllerFactory(context);
            ControllerBuilder.Current.SetControllerFactory(factory);

            // Set view engine
            MefViewEngine mefEngine = new MefViewEngine(_pluginPath, _plugins, _defaultMaster);
            ViewEngines.Engines.Clear();
            ViewEngines.Engines.Add(mefEngine);
        }

        #endregion

        #region Helper Methods

        /// <summary>
        /// Based on ideas from http://blog.codinglight.com/2009/07/assembly-resolution-with.html
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="resolveArgs"></param>
        /// <returns></returns>
        private Assembly PluginAssemblyResolve(object sender, ResolveEventArgs resolveArgs)
        {
            Assembly[] currentAssemblies = AppDomain.CurrentDomain.GetAssemblies();

            // Check we don't already have the assembly loaded
            foreach (Assembly assembly in currentAssemblies)
            {
                if (assembly.FullName == resolveArgs.Name || assembly.GetName().Name == resolveArgs.Name)
                {
                    return assembly;
                }
            }

            // Load from directory
            return LoadAssemblyFromPath(resolveArgs.Name, _fullPluginPath);
        }

        private static Assembly LoadAssemblyFromPath(string assemblyName, string directoryPath)
        {
            foreach (string file in Directory.GetFiles(directoryPath))
            {
                Assembly assembly;

                if (TryLoadAssemblyFromFile(file, assemblyName, out assembly))
                {
                    return assembly;
                }
            }

            return null;
        }

        private static bool TryLoadAssemblyFromFile(string file, string assemblyName, out Assembly assembly)
        {
            try
            {
                // Convert the filename into an absolute file name for 
                // use with LoadFile. 
                file = new FileInfo(file).FullName;

                if (AssemblyName.GetAssemblyName(file).Name == assemblyName)
                {
                    assembly = Assembly.LoadFile(file);
                    return true;
                }
            }
            catch
            {
            }

            assembly = null;
            return false;
        }

        #endregion

        private class PluginComparer : IComparer<IPluginRegistration>
        {
            public int Compare(IPluginRegistration x, IPluginRegistration y)
            {
                if (x.PluginName == y.PluginName)
                {
                    throw new Exception(string.Format("Multiple Plugins have both been registered with plugin name '{0}'.", x.PluginName));
                } 
                
                int result = x.Priority.CompareTo(y.Priority);

                if (result == 0)
                {
                    throw new Exception(string.Format("Plugins '{0}' and '{1}' have both been registered with priority '{2}'.", x.PluginName, y.PluginName, x.Priority));
                }

                return result;
            }
        }
    }
}
